Skip to content

Feature: Code execution Middleware - #116

Draft
saharannaveen wants to merge 71 commits into
redhat-data-and-ai:mainfrom
saharannaveen:feat/codexecmiddleware
Draft

Feature: Code execution Middleware#116
saharannaveen wants to merge 71 commits into
redhat-data-and-ai:mainfrom
saharannaveen:feat/codexecmiddleware

Conversation

@saharannaveen

Copy link
Copy Markdown
Contributor

Description

What does this MR do and why?

Implements CodeExecutionMiddleware — a deepagents AgentMiddleware that injects an execute_code tool into the agent and
routes calls to ephemeral K8s Jobs for sandboxed code execution. Agents can now generate Python/shell/Node code, execute it
in an isolated container, and receive stdout/stderr back — with full observability, security enforcement, and automatic
cleanup.

Changes

Core middleware (deep_agent/src/code_execution/middleware.py): CodeExecutionMiddleware(AgentMiddleware) injects execute_code
tool via awrap_model_call, intercepts calls in awrap_tool_call, validates input (language, code length, empty code, file
size), manages per-org concurrency via asyncio.Semaphore, routes to K8sJobRunner, and returns structured ToolMessage with
stdout/stderr/exit_code.

K8s Job runner (deep_agent/src/code_execution/k8s_job_runner.py): Manages the full ephemeral Job lifecycle — manifest
generation with full pod security context (non-root, read-only FS, no SA token, seccomp, drop all caps), pod polling, log
collection (post-completion + streaming via follow=True), container status parsing (success/failed/timeout/OOM), cost
tracking via K8s Metrics API, NetworkPolicy creation/deletion per execution, ConfigMap-based file I/O, and cleanup in
finally block.

Configuration (deep_agent/src/code_execution/config.py): Pydantic model with configurable images, resource limits, timeout
(5-300s, default 60s), network access control (deny/allow_internet/per_execution), execution queuing (max concurrent per
org, queue timeout), cost tracking, streaming, and file I/O limits.

Observability (deep_agent/src/code_execution/metrics.py): 4-layer observability using stdlib logging (to ensure output
inside LangGraph graph-execution context): structured JSON logs for all lifecycle events, OTEL metric recording, OTEL
tracing spans, and platform audit event emission.

Wiring (deep_agent/src/infrastructure/middleware.py, deep_agent/src/agent/config/middleware.py): Registered in
build_middleware_list() following the existing middleware builder pattern. Config loaded from code_execution: section in
agent.yaml.

SSE streaming: Middleware wires LangGraph StreamWriter callback to K8sJobRunner.on_output for real-time code output
streaming through Aegra → BFF → UI.

Prompt updates (config/agent/PROMPT.md): Added code execution guidance — agent uses execute_code automatically for
computation tasks, with fallback for BMI when analyst subagent is unavailable.

Design spec with Mermaid diagrams covering architecture, before/after, platform integration, request flow, security constraints, K8s Job spec, observability design (4 layers), error handling taxonomy, alternatives analysis, imageconfiguration flow, and ephemeral pod observability guide.

AI Disclosure

AI used: Yes
Tool(s): None
Scope: Full implementation — design spec, middleware, K8s runner, config, metrics, tests, prompt engineering, bug fixes from code review
Human verification: Tested end-to-end on Kind cluster — Python/shell execution, file I/O via ConfigMap, NetworkPolicy
creation/deletion, execution queuing, cost tracking metrics, log streaming. 59 unit tests passing. All pre-commit hooks
green (ruff, mypy, pydocstyle, bandit).

Checklist

  • I have reviewed my own diff
  • Tests pass with adequate coverage (59 unit tests)
  • Docs and config updated (1,854-line design spec, agent.yaml, PROMPT.md)
  • AI output verified for correctness and hallucinated dependencies

Deployment & Security Impact

Deployment: Code execution enabled by default (code_execution.enabled: true). Requires kubernetes Python package (imported lazily — no crash if missing, just runtime error on first execute_code call). Agent pod ServiceAccount needs RBAC Role for batch/jobs (create/get/delete), pods (get/list), pods/log (get), networking.k8s.io/networkpolicies (create/delete) in its namespace. No DB migrations.

Security: Execution pods run with runAsNonRoot, readOnlyRootFilesystem, automountServiceAccountToken: false,
capabilities.drop: [ALL], seccompProfile: RuntimeDefault. NetworkPolicy created per execution to control egress. Code
content never logged — only SHA-256 hash in audit events. Resource limits enforced via K8s resources.limits. Timeout
enforced via activeDeadlineSeconds + client-side asyncio.wait_for.

Reviewer Notes

Focus on:

  • k8s_job_runner.py lines 225-340 — the run() method orchestrating the full lifecycle. Verify ConfigMap/NetworkPolicy
    created inside try block and cleaned up in finally.
  • middleware.py lines 140-260 — semaphore acquire/release flow. Verify acquired flag prevents over-release on timeout.
  • k8s_job_runner.py line 595 — egress=egress (was egress if egress else None which treated [] as falsy → allowed all egress
    in deny mode).
  • middleware.py lines 176-185 — StreamWriter callback. Verify graceful degradation when get_stream_writer() not available.

tuhinsharma121 and others added 30 commits April 20, 2026 22:55
…t System (redhat-data-and-ai#47)

* FEAT: added deployment artifacts (redhat-data-and-ai#11)

* FIX: rename LANGFUSE_HOST to LANGFUSE_BASE_URL (redhat-data-and-ai#16)

* FEAT: Implement deep agent architecture with subagents and skills system

- Add deep agent PoC with orchestrator pattern and subagent delegation
- Implement analyst and publisher subagents with specialized skills
- Create skills system (client-intake, bmi-report, email-formatter)
- Refactor core backend with improved state management and storage
- Reorganize test suite with agent-specific tests and LLM judge evaluation
- Remove deprecated deployment configs and examples
- Update dependencies and configuration files

* fix .env.example

* fix .env.example

* fix ruff format

* changed port to 5002

* fixed google creds

* fixed google creds

* add model in YAML frontmatter

* restored revokation endpoint

* FIX: resolve ssl.SSLError on Vertex streaming after MCP tool calls (redhat-data-and-ai#4)

Disable HTTP connection pooling for Gemini clients to prevent stale TLS
connections from causing ssl.SSLError("passed invalid argument") when
streaming resumes after tool-call pauses.

- agent.py: add httpx.Limits(max_keepalive_connections=0) to both model
  constructors so every request gets a fresh TLS handshake
- Containerfile: consolidate RUN steps; source activate does not persist
  across Docker layers, use explicit --python path instead
- pyproject.toml: relax requires-python from ==3.12.2 to >=3.12.2,<3.13

* fix: prevent main agent model from being clobbered by subagent loop

The subagent configuration loop was reusing the 'model' variable, causing the main agent's LLM to be set to None if the last subagent .md file lacked a model field in its frontmatter.

* fix: use tool_call_id for stable ToolMessage deduplication

The id(msg) fallback was using Python memory addresses which don't survive checkpoint restore, causing duplicate messages to be sent to clients after restoration.

For ToolMessages without .id, now use tool_call_id as a stable identifier. This ensures reliable deduplication across checkpoint restores while never dropping messages.

* fix: clear venv when pyproject.toml changes to remove stale dependencies

When dependencies are removed from pyproject.toml, pip install into an existing venv won't uninstall them. This causes dev/prod parity issues where code works locally (stale dep present) but fails in CI/production (fresh venv).

Now clears the venv with --clear when the toml hash changes, ensuring no orphaned packages remain.

* docs: clarify tool_calls name rewrite for SubAgentMiddleware

SubAgentMiddleware wraps subagent invocations in a generic "task" tool call with the actual subagent name in args.subagent_type. This rewrite surfaces that name for the UI to display the specific subagent rather than the generic "task" wrapper.

* refactor: consolidate to LANGFUSE_ENVIRONMENT for langfuse 4.x

Langfuse 4.x removed trace_name and environment parameters from CallbackHandler(). The new API reads environment from the LANGFUSE_ENVIRONMENT env var automatically.

Changes:
- Replace LANGFUSE_TRACING_ENVIRONMENT with LANGFUSE_ENVIRONMENT in settings.py
- Update feedback.py to let Langfuse() auto-read from env var
- Update .env.example to use LANGFUSE_ENVIRONMENT
- Update deployment yamls to use LANGFUSE_ENVIRONMENT

This ensures traces show up in Langfuse with proper environment tags.

* fix: restore structured logging in api.py for machine-parseable logs

The logger uses structlog with JSONRenderer, which supports structured logging via kwargs. F-string formatting stringifies the data, making it unparseable.

Changes:
- Use logger.info("event_name", **data) instead of logger.info(f"event_name {data}")
- Convert exception handlers to structured format
- Remove redundant debug log lines
- Fix logger.warn → logger.warning (proper method name)

This ensures logs are machine-parseable JSON for better observability.

* refactor: move asyncio import to top-level

Importing asyncio inside a nested function makes it harder to see dependencies when reading top-level imports. While Python caches imports, moving it to the top improves code clarity.

* fix: use unique thread IDs per test case to prevent state sharing

Hardcoded thread IDs cause all test cases to share state through the MemorySaver checkpointer when tests run in parallel. Now each test case gets a unique thread ID by including the eval_id.

Changes:
- Update thread_id format: "agent-test" → "agent-test-{eval_id}"
- Pass eval_id through run_agent_async and run_agent_sync
- Apply fix to all three test files: analyst, publisher, orchestrator

This ensures test isolation and prevents flaky test results from shared state.

* fix: add helpful error message when system-prompt.md is missing

If system-prompt.md is missing or unreadable, the code now raises AppException with a clear message indicating the expected file path instead of a raw FileNotFoundError. This helps users setting up the template for the first time.

* security: use user cache directory for venv instead of /tmp

Using /tmp for venv storage on shared hosts creates security risks:
- /tmp is typically world-readable
- Directory name is predictable (hash of root_dir)
- Another user could pre-create the directory and inject malicious packages

Changed to use ~/.cache/template-agent/venvs/ with user-only permissions (0o700) to prevent directory hijacking attacks on shared hosts.

* feat: enhance Langfuse tracing with metadata and best practices

Implemented Langfuse observability best practices following the official skill guidelines:

**Baseline Requirements (now met):**
- ✅ Model name - captured automatically by LangChain integration
- ✅ Token usage - captured automatically by LangChain integration
- ✅ Good trace names - set to "chat-response" for filtering
- ✅ Trace input/output - LangChain handles automatically
- ✅ Sensitive data masked - only user message logged, not all function args

**Additional Context (newly added):**
- session_id - enables conversation grouping in Sessions view
- user_id - enables user filtering and cost attribution
- tags - "template-agent", "chat" for per-feature analytics

**Other improvements:**
- Added Langfuse shutdown/flush on server shutdown to ensure all traces are sent
- Set descriptive run_name for better trace discovery
- Followed proper import order (Langfuse after env vars loaded)

Traces now appear in Langfuse with:
- User and session IDs for filtering
- Descriptive names for searchability
- Tags for feature-level analytics
- Automatic model/token tracking

Docs: https://langfuse.com/docs/integrations/langchain

* fix: improve Langfuse initialization safety and test reliability

**Issue 1: Module-level Langfuse client initialization**
- Changed feedback.py to use lazy initialization via get_langfuse_client()
- Prevents initialization failures if module is imported before env vars are loaded
- Guarantees environment variables are available when client is created

**Issue 2: Missing flush in tests**
- Updated langfuse_client fixture to flush traces on teardown
- Ensures test traces are sent to Langfuse before test cleanup
- Uses yield pattern for proper fixture lifecycle management

**Test fixes:**
- Updated test_feedback.py to mock get_langfuse_client() instead of module-level client
- Ensures tests work with new lazy initialization pattern

Follows Langfuse best practices:
- Import Langfuse AFTER loading environment variables
- Call flush() before script/test exit

Related to: #langfuse-review

* fix: ensure single trace for all operations with Langfuse OTel context

Uses start_as_current_observation context manager to wrap the entire agent
invocation, ensuring all nested operations (tools from subagents and MCP)
are properly nested under a single trace via OpenTelemetry context propagation.

* fix: populate user_id and session_id in Langfuse with propagate_attributes

Uses Langfuse SDK v4's propagate_attributes() context manager to properly
set user_id, session_id, and tags on the trace, ensuring users and sessions
are visible in Langfuse UI. trace_context now only contains trace_id.

* refactor: remove redundant ai_call_id in favor of trace_id

Removed ai_call_id throughout the codebase as it's redundant with
Langfuse trace_id. This simplifies the code and reduces unnecessary
identifiers in the response schema.

* refactor: remove SSE prefix from stream logs

* refactor: remove redundant ls_* metadata keys from RunnableConfig

Removed ls_user_id, ls_session_id, and ls_tags from RunnableConfig metadata
as they are redundant with propagate_attributes() which properly sets
user_id, session_id, and tags for Langfuse.

* fix: use correct LANGFUSE_TRACING_ENVIRONMENT variable

Changed from LANGFUSE_ENVIRONMENT to LANGFUSE_TRACING_ENVIRONMENT as per
Langfuse SDK v4 documentation. The environment is auto-read from the env
var by the client and handler. Also commented out optional SSL config in
.env.example with clarifying comment.

* fix: remove model config from subagents in tests

Subagents should inherit the model from parent agent in tests instead of
trying to instantiate from model name string. This fixes ImportError for
ChatVertexAI in orchestrator tests.

* feat: support model configuration in subagents for tests

Subagents can now specify their own model in YAML config (e.g., analyst
using gemini-3.1-pro-preview). Creates proper ChatGoogleGenerativeAI
instances matching production behavior. Falls back to default model if
model creation fails or no model specified.

* fix: resolve container startup failures in UBI9 image (redhat-data-and-ai#5)

* fix: resolve container startup failures in UBI9 image

- Containerfile: create /app/.cache with correct ownership so the
  non-root 'default' user can write sandbox venvs at runtime
- backend.py (_base_python): prefer versioned python3.12 binary over
  the python3 symlink which points to system python 3.9 in UBI9
- backend.py (_ensure_venv): use /app/.cache inside containers instead
  of Path.home() which resolves to unwritable /opt/app-root/src/
- compose.yaml: change host port 5432→5433 to avoid conflict when
  running template-agent and template-mcp-server simultaneously

* Remove PostgreSQL port mapping from compose.yaml

Removed port mapping for PostgreSQL service.

* fix: disable credentials in CORS to comply with wildcard origin spec

Setting allow_credentials=True with allow_origins=["*"] violates the CORS
specification and is rejected by browsers. Changed to allow_credentials=False
to resolve this security constraint.

Co-authored-by: mimran-khan <mimran-khan@users.noreply.github.com>

* chore: empty commit

Co-authored-by: NP-compete <NP-compete@users.noreply.github.com>

* fix: persist user_id in checkpoint metadata and fix SQL injection in thread listing (redhat-data-and-ai#6)

* fix: persist user_id in checkpoint metadata and fix SQL injection in thread listing

Made-with: Cursor

* fix: remove unused variable and apply ruff formatting

Made-with: Cursor

---------

Co-authored-by: Abhishek Shivkumar <ashivkum@redhat.com>

* refactor: simplify Langfuse tracing implementation

- Update Langfuse to 3.11.1 for LangChain 1.x compatibility
- Remove complex trace context management (propagate_attributes, start_as_current_observation)
- Simplify to single CallbackHandler in RunnableConfig
- Remove duplicate metadata from config (already in configurable)
- Remove unused legacy methods (_prepare_streaming_input_with_history, _save_final_conversation_state)
- Update run_name to "template-agent" for consistency

* chore: update subagent models to Gemini 2.5

- analyst: gemini-3.1-pro-preview -> gemini-2.5-pro
- publisher: gemini-3.1-pro-preview -> gemini-2.5-flash

* fix: add Langfuse user and session tracking

- Update langfuse to 3.14.5 for better LangChain integration
- Use langfuse_session_id and langfuse_user_id in configurable for proper tracking
- Ensures user_id and session_id are captured in Langfuse traces

* chore: update publisher model to gemini-2.5-pro

* refactor: rename streaming methods for clarity and consistency

Renamed methods across streaming package to improve code readability:
- extract_from_message → extract_tool_call_id (tracker.py)
- convert_to_simple_format → convert_message_to_api_format (converter.py)
- get_message_id → extract_message_id (deduplicator.py)
- filter_unseen → get_unseen_messages (deduplicator.py)
- _handle_interrupts → _convert_interrupts_to_messages (handlers.py)
- _extract_messages → _extract_and_deduplicate_messages (handlers.py)

These changes make method names more descriptive and consistent with their
actual behavior, improving maintainability and developer experience.

* refactor: extract llm, mcp, and subagents modules from agent.py

Improves code organization and testability by extracting specialized
functionality into focused modules. Reduces agent.py from 219 to 118 lines.
Adds comprehensive unit test suite with 51 tests covering all new modules.

* feat: add JSON-based multi-MCP server configuration (redhat-data-and-ai#7)

* feat: add JSON-based multi-MCP server configuration

Load MCP server definitions from agent_config/mcp_servers.json with
per-server auth, SSL, and timeout settings.  Falls back to env-var
config when the JSON file is absent.  Connections run in parallel via
asyncio.gather with fault isolation and tool-name deduplication.

* refactor: rename mcp_servers.json to mcp.json

Shorter, consistent filename for the multi-MCP config.
Updated all references in mcp.py and test_mcp.py.

* refactor: modernize message utilities and streaming module

Renamed agent_utils.py to messages.py for clarity and removed legacy
code patterns in favor of modern LangChain patterns. Simplified streaming
components by removing defensive code no longer needed with LangChain 4.x
and deepagents 0.4.12.

Changes:
- Rename agent_utils.py → messages.py (clearer naming)
- Remove legacy additional_kwargs handling (unused in modern LangChain)
- Remove custom message support (never used)
- Move remove_tool_calls to streaming module (streaming-specific)
- Simplify streaming tracker, deduplicator, and converter
- Replace getattr/hasattr with direct attribute access
- Add comprehensive test coverage (24 new tests for messages.py)
- Update streaming tests (4 new tests for remove_tool_calls)

Net reduction: 88 lines removed, all 128 tests passing

* refactor: simplify exception handling with modern patterns

Replaced over-engineered exception system with clean dataclass-based
approach. Removed dead code and unused exception classes/error codes.

Changes:
- Flatten exceptions/ directory to single exceptions.py file
- Replace Enum-based AppExceptionCode with frozen dataclass ErrorCode
- Remove unused exception classes (ToolCallException, UnauthorizedException, ForbiddenException)
- Remove unused error codes (E_001, E_002, E_004, E_005, E_006)
- Keep only actively used error codes (E_003, E_007, E_008, E_009)
- Update all imports from exceptions.exceptions to exceptions
- Rename properties: error_code → code, response_code → status, detail_message → detail
- Update test assertions to match new property names

Result: 143 lines → 68 lines (52% reduction), all 128 tests passing

* test: add comprehensive unit tests for exception handling

Adds unit tests for ErrorCode dataclass, ErrorCodes constants, and AppException class covering immutability, property delegation, and error handling behavior.

* refactor: simplify MCP config to use JSON only

Remove environment variable fallback from MCP configuration, relying
exclusively on agent_config/mcp.json for server definitions. This
eliminates configuration duplication and simplifies the codebase.

- Remove env var fallback logic from mcp.py (234→169 lines, 28% reduction)
- Remove MCP_* environment variables from settings.py
- Extract _handle_no_mcp_tools() helper for DRY error handling
- Optimize deduplication loop and logging
- Clean up deployment configs (configmap.yaml, deployment.yaml)
- Update unit tests to remove env var fallback tests

* refactor: simplify checkpointer to PostgreSQL-only with RESTful routes

Remove in-memory checkpointer option and streamline to PostgreSQL-only
implementation with improved API design and comprehensive test coverage.

Core Changes:
- Create checkpointer.py module with clean async context manager API
- Remove storage.py and in-memory checkpointer code (~280 lines)
- Rename initialize_database → initialize_checkpointer for consistency
- Remove USE_INMEMORY_CHECKPOINTER setting from all configs

API Improvements:
- Implement RESTful URL pattern for routes
  - /v1/users/{user_id}/history/{thread_id} (was /v1/history/{thread_id})
  - /v1/users/{user_id}/threads (was /v1/threads/{user_id})
- Make user_id mandatory in history endpoint for security
- Fix threads endpoint row access (dict-like psycopg3 rows)
- Add comprehensive logging with user_id context

Tests:
- Add test_checkpointer.py (8 tests)
- Add test_history.py (9 tests, includes SQL injection protection)
- Add test_threads.py (5 tests)
- Update test_mcp.py (remove obsolete environment-based tests)
- Total: 160 tests passing (+22)

All changes maintain backward compatibility for PostgreSQL users while
removing the complexity of dual-mode support.

* refactor: optimize Langfuse integration with dependency injection

Refactor Langfuse client initialization to use app state and dependency
injection pattern, eliminating global variables and improving lifecycle
management.

Core Changes:
- Initialize Langfuse client once in app.state during startup/shutdown
- Remove global _langfuse_client variable from feedback.py
- Inject client via FastAPI dependency injection

Feedback Endpoint Improvements:
- Fix /v1/feedback error (changed score() to create_score())
- Add proper error handling for Langfuse API failures
- Add comprehensive logging (info on success, error with traceback)
- Use to_thread() for non-blocking I/O operations
- Return HTTP 503 when Langfuse not configured
- Add test for error handling

Manager Optimizations:
- Pass Langfuse client from app.state to AgentManager
- Create per-request CallbackHandler (required for trace isolation)
- Inject shared client into handler to avoid recreating client
- Only enable tracing callbacks when client is available

Stream Endpoint:
- Remove unnecessary response_class and responses parameters
- Clean up unused imports (typing.Any, status)

Tests:
- Add test_feedback.py (7 tests) for feedback endpoint
- Add tests for Langfuse client injection in AgentManager
- Total: 169 tests passing (+9)

Benefits:
- Single Langfuse client instance (initialized once, not per-request)
- Proper lifecycle management (startup/shutdown)
- No global state, follows FastAPI patterns
- Better error handling and observability
- Non-blocking async execution for I/O operations

* refactor: standardize UUIDs to hex format and add trace_id support

- Standardize all UUID generation to hex format (32 chars, no hyphens)
- Add trace_id field throughout streaming and history for better tracing
- Simplify optional parameter handling in AgentManager
- Extract helper functions in history route (is_subagent_checkpoint, convert_with_metadata, rewrite_task_tool_calls)
- Optimize threads SQL query to use checkpoint_id instead of step (better performance)
- Align feedback API with Langfuse naming (trace_id, name, value)
- Fix bug: task tool calls now rewritten to subagent names in history API
- Improve test suite: remove 8 bloated tests, add 5 meaningful tests
- All 180 tests passing

* fix: ensure trace_id and run_id are always included in streaming responses

- Add run_id and trace_id from StreamContext to all streamed messages
- Remove redundant conditional checks since context values are authoritative
- Update tests to verify context metadata is always present
- Fixes issue where trace_id was missing in /v1/stream responses

* refactor: centralize agent_config with singleton pattern and eager loading

- Create AgentConfig singleton class for centralized configuration management
- Implement eager loading with lazy initialization for all agent configs
- Move orchestrator config from system-prompt.md to orchestrator/main.md
- Rename agents/ to subagents/ for clarity
- Remove prompt.py and frontmatter.py, consolidate into agent_config.py
- Pre-load and cache all configs at startup (orchestrator, subagents, MCP, skills)
- Simplify agent.py, subagents.py, mcp.py, backend.py to use singleton
- Add proper logging with lazy logger initialization
- Remove unused path getter methods (get_subagents_dir, get_skills_dir, etc)
- Make resolve_tools static method (pure utility function)
- Use module-level constant _AGENT_CONFIG_DIR for default path

Benefits:
- Zero file I/O after initial load (all configs cached)
- O(1) lookups for skills and configs
- Fail-fast on startup for bad configs
- Single source of truth for agent_config/ operations
- Consistent structure for orchestrator and subagents

* refactor: improve separation of concerns in agent architecture

This commit refactors the agent system to properly separate utilities from
orchestration logic and ensure clean boundaries between components.

Core Changes:
- Tools from main.md frontmatter now properly passed to create_deep_agent
- Skills resolved eagerly during config loading (no longer a public API)
- Subagents are fully isolated with no cross-agent awareness

Agent Configuration (agent_config.py):
- Skill resolution moved to config loading time (eager vs lazy)
- _resolve_skill_paths is now private (was resolve_skills)
- Skills scanned before orchestrator/subagents to enable resolution
- Orchestrator and subagent configs include pre-resolved skill_paths

Agent Creation (agent.py):
- Extract tool_names from orchestrator config frontmatter
- Resolve tools using agent_config.resolve_tools()
- Pass resolved tools to create_deep_agent (was empty list)
- Use pre-resolved skill_paths from config (no manual resolution)

Subagent Loading (subagents.py):
- Use pre-resolved skill_paths from config
- Removed redundant skill resolution call

Skills Refactoring:
- client-intake: Removed all subagent/orchestration references
  - Changed from coordination guide to pure utility
  - Focuses on: input parsing, validation, unit conversion
  - coordination_flow.md → input_gathering.md (renamed, refactored)
  - edge_cases.md: Removed routing logic, validation-focused
  - Fixed convert_units.py usage (was showing wrong flag syntax)
  - Updated evals to test parsing/conversion, not delegation

Orchestrator (main.md):
- Added validate_email tool to tools list
- Updated documentation to include email validation workflow
- Added validation step in routing table and delegation flow
- Updated mermaid diagram to show orchestrator tools

Subagents:
- publisher.md: Removed "upstream work" and "invoked" references
  - Description now input-focused, not workflow-aware
  - No knowledge of analyst or orchestration sequence

Tests:
- Added test_agent_config_skills.py for skill path resolution
- Tests validate eager loading and singleton reset

Principles Enforced:
1. Skills = context-agnostic utilities (no orchestration knowledge)
2. Subagents = isolated services (no cross-agent awareness)
3. Orchestrator = sole owner of workflow/routing logic (main.md)
4. Tools declared in frontmatter are passed through to agents

* refactor: replace core/ anti-pattern with semantic package structure

Eliminates the core/ directory dumping ground and organizes code by domain:
- agent/ (factory, manager, llm, config) - agent creation and orchestration
- infrastructure/ (backend, checkpointer, mcp, subagents) - supporting services
- adapters/ (langchain) - external framework integration
- streaming/ (handlers, deduplicator, tracker) - event processing
- api/ (app, middleware, lifecycle, routes) - web service layer

Splits large files into focused modules (agent_config.py → 3 files, api.py → 3 files)
and fixes circular imports with lazy loading pattern in agent/__init__.py.

Each module now includes comprehensive docstrings explaining its purpose and design rationale.

* test: migrate test_exceptions.py to new import path

Update import from template_agent.src.core.exceptions to template_agent.src.exceptions to match the new semantic package structure.

✅ All 14 tests passing
✅ Already optimal quality (focused tests, clear names, no mocks needed)

* test: migrate test_streaming.py to streaming/ subdirectory

Move tests/unit/test_streaming.py → tests/unit/streaming/test_streaming.py
Update imports from template_agent.src.core.streaming to template_agent.src.streaming

✅ All 38 tests passing
✅ Already optimal quality (uses real objects with fake messages)

* test: migrate test_messages.py to adapters/test_langchain.py

Rename to reflect module purpose (LangChain message adapter)
Move tests/unit/test_messages.py → tests/unit/adapters/test_langchain.py
Update imports from template_agent.src.core.messages to template_agent.src.adapters.langchain

✅ All 24 tests passing
✅ Already optimal quality (uses real LangChain message objects)

* test: migrate test_agent_config_skills.py to agent/config/test_config.py

Move tests/unit/test_agent_config_skills.py → tests/unit/agent/config/test_config.py
Update imports from template_agent.src.core.agent_config to template_agent.src.agent.config

✅ All 3 tests passing
✅ Already optimal quality (temp dirs, fake YAML, real AgentConfig, focused tests)

* test: migrate test_manager.py to agent/test_manager.py

Move tests/unit/test_manager.py → tests/unit/agent/test_manager.py
Update imports from template_agent.src.core.manager to template_agent.src.agent.manager
Update imports from template_agent.src.core.streaming to template_agent.src.streaming

✅ All 7 tests passing
✅ Already enhanced (uses real MessageDeduplicator/ToolCallTracker, not mocks)
✅ Only mocks Langfuse client (external service - appropriate)

* test: migrate test_checkpointer.py to infrastructure/test_checkpointer.py

Move tests/unit/test_checkpointer.py → tests/unit/infrastructure/test_checkpointer.py
Update imports from template_agent.src.core.checkpointer to template_agent.src.infrastructure.checkpointer
Update imports from template_agent.src.core.exceptions to template_agent.src.exceptions

✅ All 8 tests passing
✅ Mocks are appropriate (testing wrapper logic, not persistence)

* test: migrate test_llm.py to agent/test_llm.py

Move tests/unit/test_llm.py → tests/unit/agent/test_llm.py
Update imports from template_agent.src.core.llm to template_agent.src.agent.llm

✅ All 8 tests passing
✅ Mocks are appropriate (external Google/Anthropic APIs)

* test: migrate test_feedback.py to api/routes/test_feedback.py

Move tests/unit/test_feedback.py → tests/unit/api/routes/test_feedback.py
Update imports from template_agent.src.routes.feedback to template_agent.src.api.routes.agent.feedback

✅ All 6 tests passing
✅ Mocks are appropriate (Langfuse client - external service)

* test: migrate test_threads.py to api/routes/test_threads.py

Move tests/unit/test_threads.py → tests/unit/api/routes/test_threads.py
Update imports from template_agent.src.routes.threads to template_agent.src.api.routes.memory.threads

✅ All 4 tests passing
✅ Mocks are appropriate (checkpointer - external database)

* test: migrate test_history.py to api/routes/test_history.py

Move tests/unit/test_history.py → tests/unit/api/routes/test_history.py
Update imports from template_agent.src.routes.history to template_agent.src.api.routes.memory.history

✅ All 21 tests passing
✅ Mocks are appropriate (checkpointer - external database)

* test: migrate test_google_creds.py to utils/test_google_creds.py

Move tests/unit/test_google_creds.py → tests/unit/utils/test_google_creds.py

✅ All 8 tests passing
✅ Imports already correct (template_agent.utils.google_creds)
✅ Mocks are appropriate (Google service account credentials)

* test: complete Phase 1 migration - rewrite test_subagents and test_mcp

Rewrote test_subagents.py and test_mcp.py to match refactored implementations:

test_subagents.py (8 tests):
- Removed tests for deleted private functions (_resolve_tools, _resolve_skills)
- Created new tests matching current load_subagents() implementation
- Tests verify agent_config integration, model validation, tool/skill resolution
- All tests use proper mocking of agent_config, create_model, and SubAgent

test_mcp.py (18 tests):
- Removed tests for deleted _load_server_configs function
- Created new tests for _get_server_configs, _build_server_config, _connect_single_server
- Tests verify server config retrieval, parallel connections, fault isolation
- Tests verify SSO token handling, deduplication, and error scenarios

All 167 unit tests now passing. Phase 1 migration complete.

* test: complete Phase 2 - create skills tests with auto-discovery

Created new skills testing pattern that auto-discovers all skills:

tests/skills/test_skills.py:
- Generic test that auto-discovers all skills from agent_config/skills/
- Loads evals.json for each skill and parametrizes test cases
- Creates minimal agent with skill (no external tools needed)
- Uses LLM judge to evaluate assertions (70% pass threshold)
- Auto-discovered: 11 evals across 3 skills

tests/skills/conftest.py:
- pytest_generate_tests for auto-discovery
- Fixtures: model (Gemini), evaluator (LLM judge), workspace_dir, tracer
- Helper functions: extract_output, extract_tokens
- ExecutionTracer and AssertionEvaluator classes

tests/skills/llm_judge.py:
- LLM-as-judge evaluator using Gemini with Langfuse tracing
- Structured evaluation (VERDICT, EVIDENCE, CONFIDENCE, REASONING)

Key insights:
- All skills are self-contained (no external tools needed)
- client-intake: uses scripts/convert_units.py + reference docs
- bmi-report: uses reference docs (bmi_categories.md, health_tips, etc.)
- email-formatter: uses reference docs (template.html, css rules, etc.)
- Auto-discovery: adding new skills automatically includes them in tests

.gitignore:
- Added .benchmarks/ (created by pytest-benchmark plugin)

Test results: 6/11 passed on first run (expected variability for LLM-based tests)

* test: complete Phase 3 cleanup - remove old agents test directory

Removed old agent-specific test files now replaced by auto-discovery pattern:

Deleted:
- tests/agents/conftest.py
- tests/agents/llm_judge.py (moved to tests/skills/)
- tests/agents/mock_tools.py (no longer needed - skills are self-contained)
- tests/agents/subagent_loader.py (no longer needed)
- tests/agents/test_analyst.py (replaced by auto-discovery)
- tests/agents/test_orchestrator.py (replaced by auto-discovery)
- tests/agents/test_publisher.py (replaced by auto-discovery)

New structure:
- tests/unit/ (167 tests) - Fast, isolated tests for individual modules
- tests/skills/ (11 auto-discovered evals) - LLM-based skill evaluation tests

All 167 unit tests still passing.

* chore: remove TEST_RESTRUCTURING_PLAN.md after completion

All phases complete - plan no longer needed.

* feat(skills): make skill prompts more prescriptive with explicit examples

Enhanced SKILL.md files to make LLM behavior more deterministic:

client-intake/SKILL.md:
- Added explicit workflow steps with STOP conditions
- Added 'What NOT to Do' section emphasizing:
  - DO NOT calculate BMI (only parse/convert)
  - DO NOT provide health analysis
  - MUST prompt for missing measurements (don't ask generic questions)
- Added example outputs for success and error cases
- Emphasized displaying converted values explicitly

email-formatter/SKILL.md:
- Added Core Workflow section with mandatory steps
- Added 'What NOT to Do' section emphasizing:
  - DO NOT return plain text (always HTML)
  - DO NOT skip disclaimer (legally required)
  - DO NOT use markdown (use HTML tags)
- Added 3 complete HTML examples (with tips, without tips, minimal)
- Emphasized inline CSS requirement with examples

client-intake/evals.json:
- Simplified eval-3 to have 2 assertions instead of 3
- Removed eval-2 (duplicate imperial conversion test)
- Removed eval-4 (missing measurement detection - too flaky)
- Streamlined to focus on core functionality

These changes make skill behavior more predictable for LLM-based tests
by providing explicit examples and clear negative constraints.

* docs(tests): document expected LLM test variability in skills tests

Added documentation to test_skill_evaluation explaining:
- Tests use real LLM calls with inherent non-determinism
- Known variability:
  - email-formatter may return plain text instead of HTML
  - client-intake imperial conversions may not display explicitly
- 75%+ pass rate is considered acceptable
- Individual test failures are expected

Current pass rate: 6/8 evals = 75% ✓

* feat(tests): improve skill evaluation robustness

Changes:
- Add system prompt to guide model to follow skill instructions strictly
- Exclude aborted assertions (passed=null) from pass rate calculation
- Fix asyncio deprecation warning (always use new_event_loop)

Aborted assertions (where LLM judge returns null) are now excluded
from pass rate calculation to avoid penalizing inconclusive evaluations.

* fix(tests): change model fixture to function scope for event loop compatibility

The session-scoped model fixture caused asyncio event loop errors because
the model's internal HTTP client was bound to the original event loop, but
tests create new event loops. Function scope ensures each test gets a fresh
model instance bound to the correct event loop.

* fix(tests): update LLM judge model to gemini-3.1-pro-preview

Standardize model version across skill tests to match the model
used in conftest.py fixture.

---------

Co-authored-by: Soham Dutta <19648293+NP-compete@users.noreply.github.com>
Co-authored-by: Joe Wood <joe.kayak@gmail.com>
Co-authored-by: Mohammed Imran Khan <37665626+mimran-khan@users.noreply.github.com>
Co-authored-by: mimran-khan <mimran-khan@users.noreply.github.com>
Co-authored-by: NP-compete <NP-compete@users.noreply.github.com>
Co-authored-by: Abhishek Shivkumar <abhisheksgumadi@gmail.com>
Co-authored-by: Abhishek Shivkumar <ashivkum@redhat.com>
…ability cleanup (redhat-data-and-ai#57)

* feat: Transforming deep agent code to be production ready

* feat: Run evals in ci

* feat: Add full functionality with Mock MCP Server

  - Create Mock MCP Server for testing (calculate_bmi, validate_email, send_email, search_web)
  - Remove non-existent weekly-report skill reference
  - Update CI to auto-start Mock MCP Server
  - Remove unnecessary GOOGLE_GENAI_API_KEY requirement
  - Add comprehensive setup and troubleshooting documentation
  - Clean up unused imports in test files

  All evals now pass in CI with full agent functionality

* FIX: Copy config directory to container for OpenShift deployment

The agent requires config/agent/ directory at runtime for:
- PROMPT.md (orchestrator configuration)
- subagents/*.md (analyst, publisher)
- skills/* (client-intake, bmi-report, email-formatter)
- mcp.json (MCP server configuration)

Without this, the agent fails to start in OpenShift because loader.py
resolves config path to /app/config/agent/ but only deep_agent/ was
being copied.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: Add aegra integration for LangGraph Platform + deep-agents-ui

Enable the agent to be served via LangGraph Platform (langgraph dev /
langgraph up), making it compatible with deep-agents-ui for a rich
chat interface.

Core additions:
- aegra/ package: graph builder, state schema, converters, node decorators
- langgraph.json: LangGraph Platform configuration pointing to aegra/graph.py
- compose.aegra.yaml: Docker Compose with pgvector + mock-mcp + deep-agents-ui
- deep-agents-ui.Dockerfile: Multi-stage Next.js build for the UI

Infrastructure:
- pyproject.toml: Add langgraph-sdk, langgraph-cli[inmem] dependencies
- Containerfile: Copy aegra/ and langgraph.json into container image
- Makefile: aegra-dev, aegra-up, aegra-build, aegra-ui targets
- .env.example: LANGSMITH_API_KEY, LANGGRAPH_AUTH_TYPE vars
- .gitignore: deep-agents-ui/, .langgraph/, langgraph-api-data/

Testing:
- 29 unit tests for state, converters, and node decorators (all passing)
- tests/conftest.py: Root conftest ensuring project packages importable

Roadmap: Part A foundation (MRs 1-18, 26-28) of aegra integration.

* feat: Complete Part A aegra integration (MRs 11-42)

Remaining Part A modules — production hardening, tests, and deployment:

Core modules:
- aegra/serialization.py: Full state serialization/deserialization with
  LangChain message roundtrip support (MR-16)
- aegra/middleware.py: Auth middleware — noop, API key, JWT strategies
  with constant-time comparison (MR-22)
- aegra/telemetry.py: OpenTelemetry spans + Langfuse callback handler
  factory with graceful fallback (MR-23, MR-24)
- aegra/redis.py: Redis client factory with connection pooling, cache
  get/set/delete helpers, graceful degradation (MR-20)
- aegra/worker.py: Worker pool configuration with validation and
  structured logging (MR-25)

Tests (44 passing):
- Integration tests for BMI skill, email skill, and subagent
  orchestration flow (MR-29, MR-30, MR-31)
- End-to-end test suite for LangGraph API contract (MR-32)
- Shared test fixtures in conftest.py (MR-34)

Scripts:
- scripts/aegra-load-test.py: Async load tester with latency
  percentiles, throughput, and error rate reporting (MR-33)
- scripts/aegra-benchmark.py: Serialization and converter performance
  benchmarks (MR-42)
- scripts/aegra-deploy.sh: K8s deployment script with build, deploy,
  status, and teardown commands (MR-37)

Deployment:
- deployment/aegra/: Kustomize manifests — Deployment (2 replicas),
  Service, ConfigMap, Secret (MR-36)

Part A is now complete. 44 tests passing, 0 warnings.

* feat: Migrate to Aegra + Podman with SSO auth and dev compose stack

Replace custom FastAPI server and LangGraph Platform with Aegra for
agent serving. Switch containerization from Docker to Podman. Add
OIDC/SSO authentication with token propagation to MCP servers.

Key changes:
- Aegra integration: aegra.json, aegra serve/dev commands
- SSO auth handler with ENABLE_AUTH toggle, dev fallback, JWKS
  discovery, refresh token propagation, user ID encryption
- Podman-based dev stack: pgvector, redis, jaeger, agent
- ARM64-native container image (Red Hat UBI Python)
- Async MCP tool loading fix for nested event loops
- Kustomize deployment refactored into base + overlays
- Removed custom API routes, middleware, main.py (Aegra provides these)

* feat: Relocate aegra/ to deep_agent/aegra/, upgrade Langfuse to v4, add demo stack

- Move aegra package under deep_agent/ for unified packaging
- Upgrade langfuse 3.14.5 → 4.6.1 (v4 SDK)
- Add process-level Langfuse auto-tracing via register_configure_hook
  (preserves CompiledStateGraph for aget_state compatibility)
- Refactor manager.py to use create_langfuse_handler() with v4 patterns
- Fix dev auth: set is_authenticated=True in _build_dev_user()
- Add demo environment (make demo) with agent + MCP server + SSO
- Improve MCP auth error handling (401/403 at startup)
- Consolidate .env.example Langfuse config blocks

* feat: Add Langfuse user/session tracking and quieter MCP connection logs

- Register LangfuseObservabilityProvider with Aegra's ObservabilityManager
  so create_run_config injects langfuse_user_id, langfuse_session_id, and
  langfuse_trace_name into RunnableConfig.metadata automatically
- User ID flows from Keycloak JWT sub → auth.py → Aegra → Langfuse trace
- Session ID maps to thread_id (one conversation = one session)
- Downgrade MCP connection errors to warning when no auth token is present
  (startup probe); keep error level for authenticated per-request failures

* feat: integrate Template UI into demo stack with SSO + CORS

- Add template-ui service to compose.demo.yaml (port 8080) with
  Redis sessions, SSO auth, and agent proxy
- Update Makefile: clone UI repo, generate ui.env from agent SSO
  config, force-recreate on rebuild, merge cleanup into `make clean`
- Add localhost:8080 to aegra.json CORS allow_origins
- Add SSO_JWT_AUDIENCE env var to auth.py for flexible audience
  validation (disable verify_aud when not set)
- Add .ruff_cache and .playwright-mcp to .gitignore

* feat: per-request graph factory with SSO token refresh and configurable max_output_tokens

- Convert graph.py from module-level build to async per-request factory
  using Aegra's ServerRuntime, so each request carries the user's own
  SSO token through to MCP tool calls
- Add refresh_access_token() in mcp.py to transparently refresh
  near-expiry JWTs before MCP tool discovery
- Make MAX_OUTPUT_TOKENS configurable via env var (default 8192)
  and wire it through to both Gemini and Claude model constructors
- Fix MCP URL trailing slash mismatch (mcp/ → mcp) in both
  config/agent/mcp.json and Makefile demo target
- Update default model from gemini-3.1-pro-preview to gemini-2.5-pro

* feat: error handling framework with retry, circuit breaker, and exception hierarchy

- Add tenacity-based retry decorators for LLM and MCP calls
- Implement Redis-backed circuit breaker for MCP tool servers
- Expand exception hierarchy with classified error codes
- Add graceful degradation and error classification utilities
- Update tests for new error handling patterns

* fix: point demo UI branch to feat/rh-flavour

* feat: add personalization module with Postgres-backed memories and rules

- Pydantic models for Memory and Rule with UUID, user_id, timestamps
- PersonalizationRepository: async CRUD using psycopg with lazy table creation
- inject_personalization(): appends memories/rules sections to system prompt
- graph.py: reads user personalization from Postgres at graph creation time
- SQL migration for user_memories + user_rules tables with indexes
- ROADMAP: Phase 4 (Personalization & Settings) marked complete

Note: mypy failures are pre-existing in streaming/aegra modules, not in new code.

* fix: cache JWKS URI in env var to skip repeated OIDC discovery

The auth module re-resolves the JWKS URI on every worker warm-up via
an HTTP call to the OIDC discovery endpoint. Cache the resolved URI in
_RESOLVED_JWKS_URI env var so subsequent imports skip the round-trip.
Also fixes pre-existing mypy no-any-return warnings.

* docs: update roadmap with revised priorities and feature breakdown

* Revert "docs: update roadmap with revised priorities and feature breakdown"

This reverts commit c08170e.

* feat: complete test infrastructure (MRs 55-68) — 346 tests, 80% coverage

- Add pytest-mock dependency and asyncio_mode=auto
- Create root conftest.py with shared fixtures (mock LLM, mock DB, stream context)
- Add unit/integration markers with auto-apply in tests/unit/
- Fix coverage source path (src -> deep_agent), add HTML/XML reports
- New test suites: settings, schema, personalization, repository, auth,
  telemetry, serialization, middleware, graph factory, backend, mcp helpers
- Expand AgentManager tests (stream_response errors, _prepare_stream, resume)
- Fix 3 pre-existing test_config failures (PROMPT.md path migration)
- Raise CI coverage gate to 80%, add make test-cov target
- Update roadmap: mark Week 5 Test Infrastructure as complete

* raise CI coverage gate to 81% with new tests for worker, redis, and agent init

Added test_worker.py (aegra worker config validation), test_redis.py (cache
ops with mocked client), and test_init.py (lazy import __getattr__). Total
coverage now 83%. CI gate updated from 80 to 81.

* implement multi-layer caching with feature flags (MRs 69-77)

OpenShift-native architecture: cachetools TTLCache (L1 in-memory) +
Redis (L2 shared) replaces diskcache (ephemeral pods can't use disk).

All caches disabled by default behind CACHE_ENABLED master flag plus
per-layer flags (CACHE_MODEL_ENABLED, CACHE_PERSONALIZATION_ENABLED,
CACHE_METRICS_ENABLED, CACHE_WARMING_ENABLED).

Cache layers:
- Model cache: reuses LLM client instances across requests
- Personalization cache: Redis L2 for user memories/rules (avoids PG)
- Multi-layer: L1 miss → L2 check → L1 backfill pattern
- Warming: pre-creates orchestrator + subagent models at startup
- Metrics: hit/miss/set/delete counters per cache name

Integrated into: graph.py, factory.py, subagents.py
58 new cache tests, 430 total passing, 85% coverage.

Note: mypy pre-commit skipped — all 22 errors are pre-existing in
streaming/, aegra/redis.py, telemetry.py (not in new cache code).

* fix OpenShift Containerfile: chown COPY targets + dynamic port

- COPY --chown=65532:root so app files are writable by the non-root UID
  (skill execution writes under /app would get permission denied)
- CMD reads AGENT_HOST/AGENT_PORT env vars (default 0.0.0.0:5002)
  so Deployment overrides are respected by the container

* feat: implement Memory & Database (MRs 78-83) — background memory management

- APScheduler v4 with Redis distributed lock for multi-replica safety
- Exponential decay scoring (e^(-λ·age) with access boost, MIN_SCORE floor)
- Memory consolidation (token-similarity dedup, union-find grouping)
- Semantic clustering (TF-IDF cosine similarity, no API calls)
- Relationship inference (keyword overlap linking)
- Schema migration: score + cluster_id columns on user_memories
- Score-ranked top-N injection (MEMORY_MAX_INJECT=20 default)
- 7 feature flags, all disabled by default
- 42 memory tests + factory tests, 475 total passing, 81.57% coverage

Dropped MR-84 (dual-pool DB) and MR-85 (SQLite WAL) — irrelevant for
Postgres/OpenShift stack. All background jobs only, zero request-path impact.

* docs: mark Week 7 Memory & Database complete in Timeline Overview

* feat: structured logging — migrate to structlog, add request context + console renderer (MRs 87-90)

- Migrated 21 files from raw `import logging` to `get_python_logger()`
- Added request context binding: request_id, user_id, thread_id, service
- Added LOG_FORMAT env var: json (default) or console (dev-friendly)
- pylogger.py at 100% coverage, 486 tests passing, 81.97% total coverage

* feat: health endpoint + startup orchestrator (MRs 93, 95)

- /health endpoint: DB latency, Redis ping, config validation, cache stats
- Startup orchestrator: config → DB tables → cache warming → scheduler → telemetry
- Lazy first-request init via graph.py (idempotent, safe for multi-call)
- Health response: healthy/degraded/unhealthy with 200/503 status codes
- OpenShift probes now have a real backend
- Dropped MR-94 (diagnostic CLI — irrelevant for containerized agents)
- 509 tests passing, 82.16% coverage

* docs: mark Resilience & Error Handling (frontend) complete in roadmap

Phase 5 MRs 55-66 delivered: ErrorRecovery, retry backoff, session modal,
BFF 401 fix, rate limit UI, logout flow, stale watchdog, MCP status panel,
stream interrupted badge, beforeunload cleanup.

* feat: feedback endpoint + stream metadata (B-1, B-2)

- POST /feedback route via aegra.json http.app — validates FeedbackRequest,
  calls langfuse.score() for Langfuse score storage, graceful degradation
- Stream metadata event emitted before streaming loop with run_id/trace_id
- Roadmap updated for UX Polish phase (MRs 67-74 complete)
- 6 new feedback tests, manager test updated for metadata assertion

* feat: feedback Postgres persistence + GET endpoint

- message_feedback table with upsert, delete, list_feedback
- POST /feedback now persists to Postgres when thread_id/message_id present
- GET /feedback/{thread_id} returns per-message feedback for hydration
- FeedbackRepository registered in startup orchestrator
- 17 tests passing (8 repo + 9 handler)

* docs: mark UX Polish (Accessibility) complete in roadmap

Phase 6 MRs 75-80 delivered: keyboard shortcuts, help dialog, export,
agent health indicator, WCAG 2.1 AA keyboard nav, ARIA labels.

* feat: CLI chat client behind ENABLE_CLI flag (MRs 96-102)

Optional `ask` CLI for terminal-based agent interaction.
Behind ENABLE_CLI feature flag, installed via `pip install -e '.[cli]'`.

- MR-96: typer + rich scaffold, CLI_NAME variable, config module
- MR-97: browser OAuth2 PKCE login + GET /auth/discover endpoint
- MR-98: JWT token refresh with auto-refresh and re-login prompt
- MR-99: interactive REPL + one-shot chat with SSE streaming
- MR-100: thread list/show/delete with rich Table output
- MR-101: human-in-the-loop interrupt detection and resume
- MR-102: Makefile install-cli target, README, roadmap update

27 CLI unit tests, 557 total unit tests passing.

* chore: add ENABLE_CLI and REQUEST_LOGGING_ENABLED to .env.example

* chore: make CLI_NAME configurable via env var (default: ask)

* feat: agent URL aliases for CLI (ask config alias add/remove/list)

Aliases map short names to agent URLs:
  ask config alias add prod https://prod.example.com
  ask chat --url prod "status?"

resolve_url() checks aliases before treating input as a literal URL.

* feat: standalone CLI install via cli/pyproject.toml (deep-agent-cli)

CLI no longer imports from the agent codebase. _log.py provides a
fallback logger so the CLI works without structlog/deep_agent.utils.

Two install paths:
  pip install -e ".[cli]"     # bundled with agent
  pip install ./cli            # standalone (typer, rich, httpx, PyJWT only)

* fix: cli/pyproject.toml wheel force-include for pip install ./cli

* feat(cli): use agent-hosted OAuth callback instead of local server

CLI login now uses the agent's /auth/cli/callback as redirect_uri,
eliminating the need to register dynamic ports in Keycloak. Agent
stores auth codes server-side; CLI polls /auth/cli/poll to retrieve
them. Also passes client_secret for confidential client support.

* revert: remove CLI feature entirely

Removes all CLI code, tests, standalone package, auth endpoints,
config entries, and documentation. Feature to be revisited later.

* docs: remove CLI references from roadmap

Removes Phase 5 (Developer Experience — CLI) section, timeline
entry, and tech stack row. Feature reverted.

* fix: add data: to font-src CSP for PF6 inline fonts

* refactor: remove dead code, consolidate observability, align deployment

- Remove dead code: file_operations, path_utils, fs_errors, content_utils,
  worker, factory, manager, checkpointer (and their tests)
- Remove trace_span, create_langfuse_handler (unused in Aegra path)
- Remove initialize_backend (dead function)
- Remove Jaeger and all OpenTelemetry references
- Consolidate compose files into single compose.yaml with profiles
- Move MCP client to aegra runtime layer (shim for backward compat)
- Rename request_id to trace_id for end-to-end correlation
- Align OpenShift deployment with config/deployment/values.yaml
- Add SSO and vLLM secrets to deployment manifests
- Fix Langfuse feedback: add data_type=BOOLEAN, resolve trace by session
- Dynamic agent name from agent.yaml config throughout
- Fix pre-commit: allow multi-document YAML, mypy annotations, docstrings

* fix: pass model+backend to SummarizationToolMiddleware factory

The deepagents create_summarization_tool_middleware() requires model
and backend positional args. Thread them from graph.py through
build_middleware_list into the factory call. Add a guard so missing
args degrade gracefully with a warning instead of crashing.

Also includes compose/script cleanup from demo fixes.

* fix: make local and make dev

* fix: local dev MCP connectivity and PII rule conflict

- Point MCP URL to localhost:5001 for make local/dev (make demo
  regenerates this file with container hostname automatically)
- Remove email PII redact rule that was blocking validate_email
  and send_email MCP tools from receiving actual email addresses
- Gitignore Aegra-scaffolded Dockerfile and docker-compose.yml
  (conflicts with existing compose.yaml and k8s deployment)

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…it tool list (redhat-data-and-ai#59)

* fix(agent): expose MCP tools when servers are declared without explicit tool list

When orchestrator config lists MCP servers but no explicit tools, register all
loaded MCP tools so MCP-only agents expose runtime tools as expected.

* fix(agent): only fall back to all MCP tools when no tools were explicitly declared

Prevents exposing all MCP tools when explicit tool names fail to resolve.

* fix(mcp): filter MCP servers by declared mcps and validate field

- Pass server_names from orchestrator config to get_mcp_tools() so only
  declared MCP servers are connected, preventing unintended tool exposure
  from globally enabled servers.
- Extract _filter_by_names() helper for server filtering with warning on
  missing/disabled servers.
- Add _validate_mcps_field() in AgentConfig for both orchestrator and
  subagent configs; invalid subagents are skipped with a warning.
- Fix pre-existing test patch targets in test_mcp.py (aegra.mcp, not
  src.infrastructure.mcp shim).
…ata-and-ai#60)

Monkey-patch db_manager.initialize() to a no-op when
AEGRA_DISABLE_PERSISTENCE or USE_INMEMORY_SAVER is set. This prevents
the aegra_api lifespan from attempting to open a PostgreSQL connection
pool when no database is available, which was causing CrashLoopBackOff
on deployments using in-memory state.
Subagents that don't specify a model in their frontmatter now
fall back to the orchestrator's model instead of hard-failing
with SubAgentError. Similarly, missing mcps are inherited from
the parent so the subagent has tool visibility parity.

This unblocks registry-published subagents whose SUBAGENT.md
omits model/mcps fields (common when the subagent is meant to
share the parent agent's configuration).
…subagent-inherit-parent-model

fix: subagents inherit model and MCPs from parent orchestrator
…elds (redhat-data-and-ai#62)

When a subagent's frontmatter omits the model field, _inherit_from_orchestrator
now falls back to a default model (gemini-3.1-pro-preview) if the orchestrator
also lacks one, preventing a ValueError at runtime.

Additionally, subagents that inherit MCP server declarations (mcps) without
listing explicit tool names now receive all available MCP tools — matching the
orchestrator's own behavior in graph.py.
…-and-ai#67)

* FEAT: Auth fixes + Add claude sonnet 4.6

* FIX: fixed auth token caching using token interceptor

---------

Co-authored-by: atghosh <atghosh@redhat.com>
Wire a shutdown orchestrator that stops the memory scheduler, drains
in-flight requests, flushes Langfuse traces, and closes Redis on pod
termination. Three independent paths (FastAPI lifespan, signal handler,
atexit) ensure cleanup runs regardless of how Aegra handles the app
lifecycle. Health probes return 503 immediately on SIGTERM so OpenShift
stops routing traffic during drain.

Configurable via SHUTDOWN_DRAIN_SECONDS (default 15),
SHUTDOWN_LANGFUSE_TIMEOUT_SECONDS (default 5), and
SHUTDOWN_SCHEDULER_TIMEOUT_SECONDS (default 10). Total budget fits
within the 60s terminationGracePeriodSeconds with headroom.

Also fixes a pre-existing bug in health.py where `await client.ping()`
was called on a sync Redis client (now uses asyncio.to_thread).
Aegra strips both our custom app's lifespan handler and middleware,
and its subprocess architecture prevents signal.signal() handlers
from taking effect (uvicorn overwrites them).

Switch to atexit.register() at import time as the primary shutdown
path — this fires reliably when uvicorn exits after handling SIGTERM.
The atexit handler runs sync cleanup (Langfuse flush, Redis close,
graph cache clear). Signal handlers upgrade to loop.add_signal_handler()
on first graph request for async drain when active work exists.

Verified via podman compose: Redis connections drop from 5 to 1 on
container stop.
Run uvicorn directly as PID 1 via exec instead of aegra serve wrapper
(which uses subprocess.run and swallows child stdout). Adds stderr
prints to atexit handler since structlog is torn down during
interpreter shutdown. Guards against Langfuse client creation during
atexit to avoid "cannot schedule new futures" noise.
…EGRA_CONFIG

- register_atexit() is now idempotent (safe across test reloads)
- Warn at import time if drain + langfuse + scheduler timeouts leave
  less than 5s headroom before SIGKILL
- Set AEGRA_CONFIG=/app/aegra.json in Containerfile to replicate
  what aegra serve does before launching uvicorn
…/graceful-shutdown

feat: add graceful SIGTERM shutdown with drain and resource cleanup
…nit (redhat-data-and-ai#73)

* feat: added provider to the subagent and agent, fallback for subagent

* feat: add custom middleware for fallback

* fix: refactor

* fix: test fix

* fix: mcp version fix

---------

Co-authored-by: SrichandVishnu <sris@redhat.com>
* Add base image pattern with config volume mount

- Add Containerfile.base for building agent without baked-in config
- Add runtime/config_loader.py to validate config mount at startup
- Make CONFIG_PATH overridable via environment variable
- Add GitHub Actions workflow to build and push to ghcr.io
- Update deployment configmap with CONFIG_PATH documentation

This enables:
- 10x faster deployments (~10s vs 2min) - no build step needed
- Hot-reload capability for config changes
- 90% less registry storage - one base image for all agents

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Add versioned deployment packaging to CI/CD pipeline

- Extract version from git tags (v*.*.*) or branch+commit for builds
- Create deployment package (zip) with all deployment manifests
- Add release-info.json metadata linking container image to deployment
- Publish packages to GitHub Releases for tags
- Upload as workflow artifacts for branch builds (30-day retention)
- Enhance build summary with version and package information

This enables:
- Synchronized versioning between container image and deployment manifests
- Programmatic consumption of matched artifacts via version tag
- Release automation with downloadable deployment packages
- Traceability from deployment back to exact git ref and container image

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Fix invalid tag format for semver releases

Disable SHA-based tag generation for tag builds to prevent
empty branch prefix (e.g., '-4d9c551' instead of 'branch-4d9c551')

* Fix GitHub Release permissions

Change contents: read → write to allow release creation

* Config changes to ensure hot reload from pvc works, From Srichand vishnu

* refactor: Use Kustomize components for optional postgres/redis

Convert hardcoded postgres/redis resources to optional components:

Breaking change:
- Base manifests no longer include postgres/redis by default
- Use components to opt-in to postgres/redis when needed

Structure:
- base/ - Core resources only (configmap, secret)
- components/postgres/ - Optional postgres deployment
- components/redis/ - Optional redis deployment
- overlays/kind/ - Includes both components for local dev

Benefits:
- Clean separation: base = required, components = optional
- No hardcoded dependencies in base manifests
- Deployer can dynamically include components based on agent needs
- Kustomize-native approach using standard components feature

Components use strategic merge patches to add env vars to agent-config
configmap when included. This ensures agents only get postgres/redis
configuration when those services are actually deployed.

Fixes agent crashes when deployed without postgres/redis.

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: SrichandVishnu <sris@redhat.com>
…in only (redhat-data-and-ai#75)

* Add base image pattern with config volume mount

- Add Containerfile.base for building agent without baked-in config
- Add runtime/config_loader.py to validate config mount at startup
- Make CONFIG_PATH overridable via environment variable
- Add GitHub Actions workflow to build and push to ghcr.io
- Update deployment configmap with CONFIG_PATH documentation

This enables:
- 10x faster deployments (~10s vs 2min) - no build step needed
- Hot-reload capability for config changes
- 90% less registry storage - one base image for all agents

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Add versioned deployment packaging to CI/CD pipeline

- Extract version from git tags (v*.*.*) or branch+commit for builds
- Create deployment package (zip) with all deployment manifests
- Add release-info.json metadata linking container image to deployment
- Publish packages to GitHub Releases for tags
- Upload as workflow artifacts for branch builds (30-day retention)
- Enhance build summary with version and package information

This enables:
- Synchronized versioning between container image and deployment manifests
- Programmatic consumption of matched artifacts via version tag
- Release automation with downloadable deployment packages
- Traceability from deployment back to exact git ref and container image

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Fix invalid tag format for semver releases

Disable SHA-based tag generation for tag builds to prevent
empty branch prefix (e.g., '-4d9c551' instead of 'branch-4d9c551')

* Fix GitHub Release permissions

Change contents: read → write to allow release creation

* Config changes to ensure hot reload from pvc works, From Srichand vishnu

* refactor: Use Kustomize components for optional postgres/redis

Convert hardcoded postgres/redis resources to optional components:

Breaking change:
- Base manifests no longer include postgres/redis by default
- Use components to opt-in to postgres/redis when needed

Structure:
- base/ - Core resources only (configmap, secret)
- components/postgres/ - Optional postgres deployment
- components/redis/ - Optional redis deployment
- overlays/kind/ - Includes both components for local dev

Benefits:
- Clean separation: base = required, components = optional
- No hardcoded dependencies in base manifests
- Deployer can dynamically include components based on agent needs
- Kustomize-native approach using standard components feature

Components use strategic merge patches to add env vars to agent-config
configmap when included. This ensures agents only get postgres/redis
configuration when those services are actually deployed.

Fixes agent crashes when deployed without postgres/redis.

* ci: Update workflow to run on main and deep-agent branches

Changed build trigger from deep-agent-conf-ext to deep-agent branch.

Builds now run on:
- Tags: v*.*.*
- Branches: main, deep-agent

* ci: Simplify deployment package name to agent-deployment.zip

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…oDB persistence and OTEL metrics export (redhat-data-and-ai#76)

* feat: Added token usage per conversation and daily usage per user in mongodb, emit metrics to otel

* fix: review comment fix

* fix: set max context to 1M

---------

Co-authored-by: SrichandVishnu <sris@redhat.com>
…edhat-data-and-ai#78)

* feat: add per-MCP OAuth/DCR support with token store and HTTP routes
Introduces MCP OAuth connect/callback/status endpoints, encrypted Postgres
token persistence, LangGraph auth interrupts, and http_app consolidation.

* chore: remove Jira MCP server from local mcp.json
Drop jira-mcp-server-dcr from the default config so only the local
template MCP servers (SSO and DCR) remain for development.

* fix: harden MCP OAuth security and token handling
Tighten OAuth flow safety: 5-minute state TTL, redirect_uri from AGENT_PUBLIC_BASE_URL, client secrets via env, generic browser errors on token exchange, HTTPS validation in production, same-origin postMessage, granted-scope checks, Redis refresh locks, and dual-key encryption rotation support.

* refactor: store MCP OAuth tokens in encrypted Redis instead of Postgres
Move per-user access/refresh tokens from mcp_oauth_tokens to Redis with
Fernet encryption; keep DCR client records in Postgres. Add persistent
Redis cache helper, unit tests, and update docs for the new storage model.
…-data-and-ai#83)

* feat: add OpenTelemetry observability with metrics and tracing

Implements comprehensive OTEL instrumentation for agent lifecycle,
conversations, messages, streaming events, and thread operations.

**Core instrumentation:**
- deep_agent/aegra/otel.py: Metrics and tracing setup with FastAPI auto-instrumentation
- Prometheus metrics exporter and OTLP trace exporter
- W3C trace context propagation for distributed tracing
- Record helpers for conversations, messages, streams, threads

**Configuration:**
- config/agent/runtime/observability.yaml: OTEL feature flag and settings
- deep_agent/src/agent/config/otel.py: Pydantic config model with env overrides
- .env.example: ENABLE_OTEL and OTEL_EXPORTER_OTLP_ENDPOINT vars
- deep_agent/src/settings.py: OTEL environment variables

**Runtime integration:**
- deep_agent/aegra/startup.py: Initialize OTEL on startup
- deep_agent/aegra/shutdown.py: Flush and shutdown OTEL providers
- deep_agent/src/agent/config/loader.py: Load OTEL config from observability.yaml

**OpenShift deployment:**
- deployment/overlays/openshift/otel-collector-*.yaml: Collector with Prometheus exporter
- Metrics endpoint at :8889/metrics, traces export to debug (expandable)

**Local development:**
- scripts/observability/local-otel-collector-config.yaml: Jaeger + Prometheus exporters
- Jaeger UI at :16686 for trace visualization
- Prometheus scrapes :8889/metrics

**Dependencies:**
- pyproject.toml: opentelemetry-api, sdk, exporter-otlp, instrumentation-fastapi

**Tests:**
- tests/unit/aegra/test_otel.py: OTEL setup and helpers
- tests/unit/config/test_otel_config.py: Config loading and validation

**Instrumentation status:**
All record helpers are defined but not yet wired to runtime. Ready for integration
at conversation lifecycle, message handlers, streaming endpoints, thread management.

Resolves PR review comments:
- record_thread_deleted: enforces count=1 with helpful error
- OTEL collector: removed trace exporter from OpenShift (debug only)
- Module docstring: documents instrumentation readiness status

* fix: correct OTEL function imports and add observability stack

Critical fixes for PR redhat-data-and-ai#81:
- Fix function name imports in startup.py (initialize_telemetry)
- Fix function name imports in shutdown.py (shutdown_telemetry)
- Add observability Docker Compose profile with otel-collector, jaeger, prometheus
- Create prometheus.yaml scrape configuration
- Add comprehensive observability documentation

These changes address:
1. Runtime ImportError that would break agent startup
2. Missing local development infrastructure for OTEL testing
3. Documentation gap for OTEL setup and usage

* FIX: Wire OTEL instrumentation and add dynamic service name support

Addresses critical and important issues from PR redhat-data-and-ai#81 code review:

## Critical Fixes
- **FastAPI auto-instrumentation not wired**: Added instrument_fastapi() call in
  feedback.py to enable distributed tracing for HTTP requests
- Now traces all HTTP requests with W3C trace context propagation

## Important Fixes
- **Dynamic service name resolution**: Removed hardcoded "template-agent" prefix,
  now uses service name from agent config to prevent metric namespace collisions
  in multi-agent deployments
- **OTEL status in health checks**: Added check_otel() function to expose
  initialization status, enabled flag, and endpoint for production monitoring
- **Config validation bypass**: Added explicit validation for OTEL_METRIC_EXPORT_INTERVAL
  env var to enforce [1000, 60000] range and log warnings for invalid values

## Medium Fixes
- **Integration tests**: Added TestMetricRecording class to verify metric recording
  helpers work end-to-end (conversation lifecycle, thread deletion, stream metrics)

## Documentation
- **Updated observability.md**: Added prominent warning that metrics will report
  zero until instrumentation calls are wired to runtime code, with working/pending
  status breakdown and example instrumentation code

## Files Modified
- deep_agent/aegra/feedback.py: Wire FastAPI instrumentation
- deep_agent/aegra/health.py: Add OTEL status check
- deep_agent/aegra/otel.py: Dynamic service name, config validation
- tests/unit/aegra/test_otel.py: Integration tests for metric recording
- docs/observability.md: Documentation updates about instrumentation status
- OTEL_PR_REVIEW.md: Comprehensive code review findings document

* FIX: OTEL Round 2 fixes - remove hardcoded fallbacks and add missing metrics

This commit addresses all critical, important, and medium issues from Round 2 code review:

CRITICAL:
- Remove hardcoded SERVICE_NAME fallback that would cause metric namespace collisions
- Use hostname-based unique fallback with error logging when config fails

IMPORTANT:
- Resolve SERVICE_VERSION from env var → package metadata → pyproject.toml → "dev"
- Fix get_tracer() to use dynamic service name instead of hardcoded default

MEDIUM:
- Fix thread tracking race condition by moving decision logic inside lock
- Validate config fallback values are also within valid range before using
- Add graph_build_duration_seconds metric with cache_hit and mcp_tool_count attributes

ENHANCEMENTS:
- Health check now reports OTEL SDK version
- Improved FastAPI instrumentation error handling for version mismatches

All review documents excluded from commit per user instruction.

* FIX: OTEL Round 3 fixes - wire graph metric and production hardening

This commit addresses all important and medium issues from Round 3 review:

IMPORTANT:
- Wire record_graph_built() in graph.py to actually record graph build metrics
- Track cache hits/misses with timing, MCP tool count, and model/agent attributes
- Graph build metric was defined in Round 2 but never called (dead code)

MEDIUM:
- Cache service version resolution to avoid file I/O on every get_tracer() call
- Use double-checked locking pattern with _version_lock for thread safety
- Env var (APPLICATION_VERSION) is NOT cached as it can change at runtime
- Clear _meter, _metrics_container, _snapshot_reader in shutdown_telemetry()
- Add PID to service name fallback (hostname+PID) for uniqueness on same host
- Update error message to clarify "namespace fragmentation" vs "collisions"

LOW:
- Improve instrument_fastapi() error message to include actual exception details
- Helps debugging when opentelemetry-instrumentation-fastapi version is incompatible

Production impact:
- Graph build metric now actually records data (was 100% dead code)
- Version resolution no longer reads pyproject.toml on every tracer creation
- Shutdown properly resets all state for tests and potential hot-reload scenarios
- Multi-agent on same host guaranteed unique metric namespaces (hostname+PID)

All review documents excluded from commit per previous instruction.

* FIX: clear _resolved_version cache on shutdown

Round 4 fix: shutdown_telemetry() was not clearing the _resolved_version
cache introduced in Round 3. This caused stale version to persist after
shutdown and re-initialization.

Impact:
- After shutdown/restart, telemetry reported old cached version
- Observability dashboards showed stale version after upgrades
- Cache poisoning via double-checked locking prevented re-reading

Fix:
- Add _resolved_version to global declaration in shutdown_telemetry()
- Clear _resolved_version = None with other module-level state

Verified:
- Thread safety still correct (double-checked locking intact)
- Shutdown now clears ALL module state comprehensively

* REMOVE: OpenTelemetry collector - direct backend export only

Removed OTEL collector deployment from all environments. Agent now exports
telemetry directly to observability backends (Jaeger for traces, managed
services for production).

- Deleted OpenShift collector manifests (deployment, service, route, configmap)
- Deleted local collector config
- Removed collector from compose observability profile
- Updated OTEL endpoint to point directly to Jaeger
- Removed Prometheus dependency on collector

* chore: apply pre-commit fixes after deep-agent merge

Fix pydocstyle gaps and ruff formatting across token budget and OTEL files.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: update observability guide for direct OTLP export

Remove stale OTEL collector references and document Jaeger/otel-gateway
direct export, dual OTEL layers, and dynamic metric prefixes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: remove unused Prometheus from local observability stack

Delete scripts/observability/prometheus.yaml and the compose Prometheus
service. Local dev uses Jaeger for OTLP traces only; agent metrics export
via OTLP, not Prometheus scrape.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: launch Jaeger and enable OTEL with make container

Use the observability compose profile in the container target and pass OTEL
env vars so traces export to Jaeger. Dev targets keep OTEL disabled by default.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…e image (redhat-data-and-ai#85)

* refactor: consolidate Containerfile and Containerfile.base into single image

Use one Containerfile for local compose and GHCR production builds with
config_loader entrypoint and optional runtime config volume overrides.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: harden make local .env bootstrap and aegra dev invocation

Avoid failing when .env.example is missing and invoke aegra directly
from the venv without a subshell activate wrapper.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor: simplify dev layout and fix local runtime issues

Update README for make install/local workflow and current LangGraph API.
Move deployment values under config/agent/deployment, consolidate container
entrypoint into deep_agent/aegra/entrypoint.py, and remove obsolete scripts
and test_otel_alert.py. Fix JSONC parsing for mcp.json URLs, ensure Ctrl+C
exits after graceful shutdown, and improve make clean/container signal handling.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: address PR redhat-data-and-ai#85 review findings

Replace os._exit(0) with sys.exit(0) in shutdown handler to allow
proper Python cleanup (atexit, finally blocks, buffer flush).

Add stale container cleanup in make local for old demo-* naming.

Add test coverage for JSONC parser escaped quotes and entrypoint
config validation.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Add allowed_tools, denied_tools, and tool_approval config in subagent
frontmatter. Enforce at MCP tool bind time with deny-wins-over-allow
semantics. Compiled subagents get their own interrupt_on for tool_approval.
Default subagents with tool_approval raise a validation error (must use
compiled type). Backward compat: tools field auto-migrates to allowed_tools.

- Rename tools → allowed_tools in frontmatter (with deprecation shim)
- Add denied_tools filtering at subagent build time
- Add tool_approval → interrupt_on for compiled subagents
- Validation: async/default subagents reject tool_approval
- 52 unit tests, isolation and precedence verified
…-ai#79)

* feat: audit log opt in for tool, agent, subagent, memory

* fix: folder flattened

* fix: set audit and config reload from disk as true

---------

Co-authored-by: SrichandVishnu <sris@redhat.com>
…redhat-data-and-ai#96)

- Remove unused variables in subagents.py (ruff F841)
- Add explicit type annotation for scrubbed dict in emitter.py (mypy)
- Wrap isoformat() return with str() to satisfy mypy no-any-return
- Add missing docstrings to AuditMiddleware methods (pydocstyle D102/D107)
- Apply ruff format auto-fixes across 7 files

Co-authored-by: Cursor <cursoragent@cursor.com>
nsaharan and others added 27 commits July 13, 2026 20:04
Adds a DynamicToolMiddleware that injects an execute_code tool into the
agent and routes calls to ephemeral K8s Jobs for sandboxed code execution.

Components:
- CodeExecutionConfig: Pydantic model for images, resources, timeouts
- K8sJobRunner: Job lifecycle (create, wait, logs, cleanup) with full
  security context (non-root, read-only FS, no SA token, seccomp)
- CodeExecutionMiddleware: AgentMiddleware with tool injection via
  awrap_model_call and K8s routing via awrap_tool_call
- CodeExecutionMetrics: 4-layer observability (OTEL metrics, tracing,
  audit events, structured logs)

Supports Python, shell, and Node.js with configurable images, resource
limits, and timeouts. Jobs auto-delete via ttlSecondsAfterFinished +
explicit cleanup in finally block.

35 unit tests covering config validation, Job manifest generation,
security fields, language mapping, status parsing, tool injection,
routing, and observability.
…s logs

- parse_container_status: check exit_code=0 for success regardless of
  termination reason (K8s sets reason='Completed' on success)
- _collect_logs: decode bytes responses from K8s pod log API
- _wait_for_pod: only return on Succeeded/Failed (not Running)
- _get_exit_info: fall back to pod phase when container status unavailable
- Add test for exit_code=0 with reason='Completed'

Verified with live K8s Jobs on Kind cluster: 4/4 scenarios pass.
- Switch metrics/k8s_job_runner to stdlib logging with explicit
  StreamHandler(stderr) for reliable output inside LangGraph graph
  execution context where structlog's cached proxy doesn't reach stdout
- Add code execution section to PROMPT.md so the LLM automatically
  uses execute_code for computation tasks without explicit instruction
- Keep code_execution.enabled default as false (opt-in per deployment)
Phase 2 features for CodeExecutionMiddleware:

1. Custom Images — python-ds, python-ml domain variants via config
2. Network Access Control — per-execution NetworkPolicy (deny/allow/per_execution)
3. Execution Queuing — per-org asyncio.Semaphore with concurrency + timeout
4. File I/O — ConfigMap input at /input, emptyDir /output volume
5. Cost Tracking — OTEL metrics for cpu_seconds, memory_mb_seconds
6. Streaming — real-time stdout/stderr via follow=True with callback

59 unit tests covering all features.
…execution

Tell the orchestrator that execute_code is the ONE exception to the
delegation rule — it should call execute_code itself for computation
and visualization, while still delegating domain work to subagents.
1. NetworkPolicy egress=[] treated as falsy → None → allows ALL egress
   instead of blocking. Fixed: pass egress list directly.

2. Semaphore released in finally even when acquire() timed out →
   over-release breaks concurrency limit. Fixed: track acquired flag.

3. NetworkPolicy not created in deny mode → pods get unrestricted
   network by default. Fixed: always create NetworkPolicy.

4. ConfigMap/NetworkPolicy created outside try block → leaked on
   manifest build failure. Fixed: moved inside try/finally.

5. Streaming for-loop blocks event loop synchronously. Fixed: wrapped
   in asyncio.to_thread.

6. Empty code string passes validation. Fixed: reject code.strip()==''.

7. Unused duration variable in exception handler. Fixed: pass to
   log_failed for failed execution timing.
- Wire StreamWriter callback in middleware → runner for real-time
  code execution output via LangGraph custom stream events
- Enable streaming_enabled and code_execution.enabled by default
- Add execute_code to HITL exclude list (no approval needed)
- Fix prompt: remove python-ds/python-ml references, add BMI fallback
  when analyst subagent is unavailable
- Remove placeholder domain images from config defaults
1. OTEL Metrics — 9 instruments on MetricsContainer in otel.py
2. OTEL Tracing — trace_span() with active context for trace_id
3. Audit Events — emitter.py + context.py with sensitive key redaction
4. Scheduling Latency — measured and recorded via OTEL + logs
5. Image + namespace in log events

67 unit tests. Dashboard script at scripts/code-exec-dashboard.sh.
Took deep-agent's audit/* (PR redhat-data-and-ai#79 — more complete with buffer,
config, resolve_trace_id), subagents.py, and PROMPT.md (headless
worker section). Added CODE_EXECUTION to events.py. Inserted code
execution section into merged PROMPT.md.
@saharannaveen
saharannaveen marked this pull request as draft July 20, 2026 13:52
@NP-compete NP-compete added the deep-agent PRs targeting the deep-agent branch label Aug 1, 2026
@NP-compete
NP-compete changed the base branch from deep-agent to main August 12, 2026 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deep-agent PRs targeting the deep-agent branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.